﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NHINDirect
{
    public struct StringSegment
    {
        public static readonly StringSegment Null = new StringSegment(null);
        
        string m_source;
        int m_startIndex;
        int m_endIndex;
        
        public StringSegment(string source)
        {
            this.m_source = source;
            this.m_startIndex = 0;
            if (source == null)
            {
                this.m_endIndex = -1;
            }
            else
            {
                this.m_endIndex = source.Length - 1;
            }
        }
        
        public StringSegment(string source, int startIndex, int endIndex)
        {
            if (source == null)
            {
                throw new ArgumentNullException();
            }
            if (startIndex < 0 || startIndex > source.Length)
            {
                throw new ArgumentException("startIndex");
            }
            if (endIndex < -1 || endIndex >= source.Length)
            {
                throw new ArgumentException("endIndex");
            }

            this.m_source = source;
            this.m_startIndex = startIndex;
            this.m_endIndex = endIndex;
        }
        
        public char this[int index]
        {
            get
            {
                return this.m_source[this.m_startIndex + index];
            }
        }
                
        public string Source
        {
            get
            {
                return this.m_source;
            }
        }
        
        public int Length
        {
            get
            {
                return (this.m_endIndex - this.m_startIndex + 1);
            }
        }
        
        public bool IsEmpty
        {
            get
            {
                return (this.Length == 0);
            }
        }
        
        public bool IsNull
        {
            get
            {
                return (this.m_source == null);
            }
        }
        
        public int StartIndex
        {
            get
            {
                return this.m_startIndex;
            }
            internal set
            {
                if (value > this.m_endIndex)
                {
                    throw new ArgumentException();
                }
                
                this.m_startIndex = value;
            }
        }

        public int EndIndex
        {
            get
            {
                return this.m_endIndex;
            }
        }
        
        public override string ToString()
        {
            int length = this.Length;
            if (length == 0)
            {
                return string.Empty;
            }
            //
            // If the segment covers the entire string, we don't need an allocation
            //
            if (length == this.m_source.Length)
            {
                return this.m_source;
            }

            return this.m_source.Substring(this.m_startIndex, length);
        }
        
        public void Union(StringSegment segment)
        {
            if (segment.m_source == null)
            {
                throw new ArgumentNullException("Cannot union with null segment");
            }
            
            if (this.m_source == null)
            {
                this.m_source = segment.m_source;
                this.m_startIndex = segment.m_startIndex;
                this.m_endIndex = segment.m_endIndex;
                return;
            }
            
            if (!object.ReferenceEquals(this.m_source, segment.m_source))
            {
                throw new InvalidOperationException("Segments from different strings");
            }

            if (segment.m_startIndex < this.m_startIndex)
            {
                this.m_startIndex = segment.m_startIndex;
            }

            if (segment.m_endIndex > this.m_endIndex)
            {
                this.m_endIndex = segment.m_endIndex;
            }
        }
        
        public string Substring(int startAt)
        {
            int length = this.m_endIndex - startAt + 1;
            if (length < 0)
            {
                throw new IndexOutOfRangeException();
            }
            return this.m_source.Substring(startAt, length);
        }

        public string Substring(int startAt, int length)
        {
            int endIndex = startAt + length;
            if (endIndex < this.m_startIndex || endIndex > this.m_endIndex)
            {
                throw new IndexOutOfRangeException();
            }
            return this.m_source.Substring(startAt, length);
        }
        
        public bool Equals(string other)
        {
            if (this.Length != other.Length)
            {
                return false;
            }
            return (string.Compare(this.m_source, this.m_startIndex, other, 0, other.Length, StringComparison.OrdinalIgnoreCase) == 0);
        }
        
        public bool Equals(StringSegment other)
        {
            if (this.Length != other.Length)
            {
                return false;
            }
            return (string.Compare(this.m_source, this.m_startIndex, other.Source, other.StartIndex, this.Length, StringComparison.OrdinalIgnoreCase) == 0);
        }
        
        public bool StartsWith(string other)
        {
            int length = this.Length;
            if (other.Length > length)
            {
                return false;
            }
            
            return (string.Compare(this.m_source, this.m_startIndex, other, 0, other.Length, StringComparison.OrdinalIgnoreCase) == 0);
        }
        
        public int IndexOf(string other)
        {
            int length = this.Length;
            if (other.Length > length)
            {
                return -1;
            }
            
            return this.m_source.IndexOf(other, this.m_startIndex, length);
        }
    }
}
